package quiz;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * An Account object represents a completed, database-entered account.
 * It contains a name, a username, a hashed password, and a boolean
 * @author nishantkarandikar
 *
 */
public class Account {

	/* ===== STATIC FUNCTIONS AND VARIABLES ===== */
	
	public static int ANONYMOUS_ACCOUNT_ID = -1;

	/**
	 * Function takes in a user-entered password and returns a salted, hashed 
	 * password string. 
	 * @param vanillaPassword is the user-entered password
	 * @return a string of length 40 - the hash of the salted vanilla password
	 */
	public static String hashPassword(String vanillaPassword){

		// Salts the password //
		vanillaPassword = SALT + vanillaPassword;

		// Generates MessageDigest object //
		MessageDigest hasher = null;
		try {
			hasher = MessageDigest.getInstance("SHA");
		} catch (NoSuchAlgorithmException e1) {
			System.out.println("Error in creating MessageDigest for password \""+ 
					vanillaPassword +
			"\" - Account class");
		}

		// Generates byte array from salted password //
		byte[] stringBytes = {};
		try{
			stringBytes = hasher.digest(vanillaPassword.getBytes());
		} catch (Exception e){
			System.out.println("Error in hashing password: \"" + 
					vanillaPassword +
			"\" - Account class");
		}

		// Returns a string hex representation of the byte array //
		return hexToString(stringBytes);
	}

	// Converts a byte array to a string representing its hexadecimal value //
	private static String hexToString(byte[] bytes) {
		StringBuffer buff = new StringBuffer();
		for (int i = 0; i < bytes.length; i++) {
			int val = bytes[i];
			val = val & 0xff;  // remove higher bits, sign
			if (val<16) buff.append('0'); // leading 0
			buff.append(Integer.toString(val, 16));
		}
		return buff.toString();
	}	

	//for Cayman, Andrew, Nishant
	private static final String SALT = "can";

	/**
	 * Function sets the DataBase object that will be referenced by all accounts
	 * MUST BE CALLED BEFORE ANY ACCOUNTS ARE CREATED
	 * @param db - the DataBase object referenced by all accounts 
	 */
	public static void setDataBase(DataBase database){
		db = database;
		db.open();
	}
	public static DataBase db;

	/* ===== STATIC FUNCTIONS AND VARIABLES ===== */




	private int id;
	private String name;
	private String username;
	private String password_hash;
	private boolean is_admin;

	public Account(String name, String username, String password, boolean is_admin){
		this.name = name;
		this.username = username;
		this.password_hash = password;
		this.is_admin = is_admin;
		
	}

	/**
	 * Returns whether this account has admin privileges.
	 * @return a boolean representing whether this particular user is an admin
	 */
	public boolean isAdmin(){return is_admin;}
	
	
	
	/* =============== SETTERS ==================== */

	
	
	/**
	 * Gives this account admin privileges.
	 * @param boolean - set whether this account has admin
	 */
	public void setAdmin(boolean is_admin){
		this.is_admin = is_admin;
		db.editAccount(this);
	}
	
	public void setID(int num) {
		this.id = num;
	}
		
	
	/* =============== SETTERS ==================== */
	
	/* =============== GETTERS ==================== */
	
	/**
	 * Checks all the achievements the quizzer could have won
	 * up to date and then returns them in arraylist.
	 * 
	 * WARNING: SLOW. Use getAchievemets() if you don't need to check
	 * if there are new achievements
	 * @return new achievement or null if none
	 */
	public ArrayList<Achievement> checkAchievements() {
		return Achievement.checkAchievements(this);
	}
	
	
	public String getHashedPassword() { return this.password_hash; }
	
	public int getID() { return id; }
	
	/**
	 * Returns the String name associated with this account.
	 */
	public String getName(){return name;}
	
	/**
	 * Returns the String username associated with this account;
	 */
	public String getUsername(){return username;}

	/**
	 * Changes the password of this account.
	 * @param newpass New password edited by the user
	 */
	public void changePassword(String newPass){
		String hashedNewPass = hashPassword(newPass);
		password_hash = hashedNewPass;
		db.editAccount(this);
	}
	
	/**
	 * Faster -- call when you need achievements
	 * @return Arraylist of achievements in database
	 */
	public ArrayList<Achievement> getAchievements() {
		return db.getRecentAchievements(Integer.MAX_VALUE);
	}
	

	/**
	 * Checks if a user-entered password matches the password for this particular account
	 * @param possiblePassword A password entered by the user
	 * @return true if the given password matches the account's password, false otherwise
	 */
	public boolean passwordMatch(String possiblePassword){
		String possibleHash = hashPassword(possiblePassword);
		if (possibleHash.equals(password_hash)) return true;
		return false;
	}


	/**
	 * Adds a friend to this account's friend list.
	 * @param friend a non-null Account object to add to this account's friend list.  Must not be this Account.
	 */
	public void addFriend(int friendID){
		//acct user, acct friend
		if(friendID != this.id) {
			db.storeFriend(this.id, friendID);
		}
	}
	
	/**
	 * Gets all friends associated to this user (to and from)
	 * @return an ArrayList of Account IDs (Integer) that this user is friends with
	 */
	public ArrayList<Account> getFriends(){return db.getFriends(this);}
	
	/**
	 * Gets all Messages associated to this user (to and from)
	 * @return an ArrayList of Message IDs (Integer) belonging to this user 
	 */
	public ArrayList<Message> getSentMessages(){return db.getUserSentMessages(this.id);}

	public ArrayList<Message> getReceivedMessages(){return db.getUserReceivedMessages(this.id);}

	/**
	 * Removes a friend from this user's friend list.
	 * @param friend a non-null Account object
	 */
	public void removeFriend(int friendID){
		if (friendID != this.id) {
			db.unfriend(this.id, friendID);
		}
	}
	
	public ArrayList<Result> getFriendsRecentResults(int num) {
		return db.getFriendsRecentResults(this.id, num);
	}
	
	/**
	 * Returns a string representation of this Account's 
	 * id, name, [user, pass]
	 */
	public String toString(){
		String result = "";
		
		result += "Account " + id + ": " + name + "[" + username + ", " + password_hash + "]";
		
		return result; 
	}

	
	//id based
	
	
	/**
	 * Hashes an account object
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		result = prime * result + (is_admin ? 1231 : 1237);
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result
				+ ((password_hash == null) ? 0 : password_hash.hashCode());
		result = prime * result
				+ ((username == null) ? 0 : username.hashCode());
		return result;
	}
	
	/**
	 * Cross-compares the fields name, username, password_hash, is_admin, and id to determine
	 * whether two Account objects are identical
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Account other = (Account) obj;
		if (id != other.id)
			return false;
		if (is_admin != other.is_admin)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (password_hash == null) {
			if (other.password_hash != null)
				return false;
		} else if (!password_hash.equals(other.password_hash))
			return false;
		if (username == null) {
			if (other.username != null)
				return false;
		} else if (!username.equals(other.username))
			return false;
		return true;
	}
}